package exportdemo;

import static java.util.stream.Collectors.toList;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.function.Function;
import java.util.stream.IntStream;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


/**
 * <p>Title: IExportData </p>
 * <p>Description: 功能描述：数据导出功能接口 </p>
 * @author  Nelson
 * @date  2020/6/30 11:30
 * @version 1.0
 */
public interface IExportData {
  
  final Logger logger = LoggerFactory.getLogger(IExportData.class);

  static final int DATABASE_SEARCH_LIMIT = 1000;
  static final int LIMIT_OF_EXPORT_DATA = 10000;
  static final int DEFAULT_DATA_SAVE_DATE = 32;
  static final String BASIC_EXPORT_PATH = "/home/ms/attachment/csms/logbackups";
  
  String getFileName();
  
  String[] getHeaders();
  
  List<String[]> handleExportData(List<? extends Object> exportData);
  
  default String getSeperator() {
    return ExportUtils.DEFAULT_SEPERATOR;
  }
  
  default String getBasicExportPath() {
    return BASIC_EXPORT_PATH;
  }
  
  default String getAppName() {
    return "BASE";
  }
  
  default String buildFullPath(String appName, String fileName, String basicExportPath) {
    return String.join("/", basicExportPath, appName, LocalDate.now().toString(), fileName);
  }
  
  default String buildCSVFileName(String fileName) {
    return fileName.concat("_").concat(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"))).concat(".csv");
  }
  
  default void execute(List<? extends Object> exportData) {
    if (exportData == null || exportData.isEmpty()) 
      logger.info("{} -- 没有需要导出的数据", this.getClass().getSimpleName());
    else
      export(handleExportData(exportData));
  }
  
  default void export(List<String[]> exportData) {
    if (exportData.size() <= LIMIT_OF_EXPORT_DATA) {
      //导出本地CSV文件
      logger.info("{} -- 将数据导出到CSV -- start", this.getClass().getSimpleName());
      ExportUtils.exportFile(buildFullPath(getAppName(), buildCSVFileName(getFileName()), getBasicExportPath()),
          getHeaders(), exportData, getSeperator());
      logger.info("{} -- 导出数据 {} 条", this.getClass().getSimpleName(), exportData.size());
      logger.info("{} -- 将数据导出到CSV -- end", this.getClass().getSimpleName());
    }
    else {
      //数据量>10000，使用多线程分批导出
      int dataSize = exportData.size();
      float number = (float)dataSize / LIMIT_OF_EXPORT_DATA;
      int splitNum = (int)Math.ceil(number);// 向上取整

      List<CompletableFuture<Void>> futureTasks = 
        IntStream.range(1, splitNum + 1)
                 .parallel()
                 .mapToObj(index -> {
                   return CompletableFuture.runAsync(() -> {
                           StringBuilder log = new StringBuilder();
                           log.append("\n").append(String.format("%s -- 将数据分批次导出到CSV, 第 %s 部分 -- start", this.getClass().getSimpleName(), index)).append("\n");
                            
                            int fromIndex = (index - 1) * LIMIT_OF_EXPORT_DATA;
                            int toIndex = (index * LIMIT_OF_EXPORT_DATA) >= exportData.size()
                                          ? exportData.size()
                                          : index * LIMIT_OF_EXPORT_DATA;
                            // 导出本地CSV文件
                            ExportUtils.exportFile(buildFullPath(getAppName(), buildCSVFileName(getFileName().concat("_".concat(String.valueOf(index)))), 
                                    getBasicExportPath()), getHeaders(), exportData.subList(fromIndex, toIndex), getSeperator());
                            
                            log.append(String.format("%s -- 第 %s 部分，导出数据 %s 条", this.getClass().getSimpleName(), index, exportData.subList(fromIndex, toIndex).size())).append("\n");
                            log.append(String.format("%s -- 将数据分批次导出到CSV, 第 %s 部分 -- end", this.getClass().getSimpleName(), index));
                            logger.info(log.toString());
                          });})
                 .collect(toList());
      
      CompletableFuture.allOf(futureTasks.toArray(new CompletableFuture[futureTasks.size()])).join();
    }
  }
  
  static List<String[]> covertExportData(
      Function<List<? extends Object>, List<String[]>> exportDataConvertMapper, List<? extends Object> exportData, boolean notPaging) {
    if (notPaging || exportData.size() <= DATABASE_SEARCH_LIMIT) 
      return exportDataConvertMapper.apply(exportData);
      
    
    //oracle in语法 支持最大值是1000，超出此数量需要分页查询
    float number = (float)exportData.size() / DATABASE_SEARCH_LIMIT;
    int splitNum = (int)Math.ceil(number);// 向上取整
      
    return
      IntStream.range(1, splitNum + 1)
               .parallel()
               .mapToObj(index -> {
                 int fromIndex = (index - 1) * DATABASE_SEARCH_LIMIT;
                 int toIndex = (index * DATABASE_SEARCH_LIMIT) >= exportData.size()
                               ? exportData.size()
                               : index * DATABASE_SEARCH_LIMIT;
                               
                 return exportDataConvertMapper.apply(exportData.subList(fromIndex, toIndex));})
               .flatMap(list -> list.stream())
               .collect(toList());
  }
  
  
  
  public enum ExportDataErrorCode {
    
    CALL_CSO_CLIENT_FAIL("01000000", "CSO微服务调用失败"),
    
    ;
    
    private String code;
    private String message;

    ExportDataErrorCode(final String code,final String message) {
        this.code = code;
        this.message = message;
    }

    public String getCode() {
        return this.code;
    }
    
    public String getMessage() {
      return this.message;
    }
  }
  
  public static void main(String[] args) {
    StringBuilder builder = new StringBuilder();
    builder.append(String.format("%s -- 将数据分批次导出到CSV, 第 %s 部分 -- start", "ljs", 1)).append("\n");
    builder.append(String.format("%s -- 第 %s 部分，导出数据 %s 条", "ljs", 1, 1000)).append("\n");
    builder.append(String.format("%s -- 将数据分批次导出到CSV, 第 %s 部分 -- end", "ljs", 1));
    System.out.println(builder.toString());
  }
  
}
